﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.ComponentModel;

using HelixToolkit.Wpf;
using EQEmu.Display3D;
using EQEmu.Doors;
using EQEmu.Database;
using EQEmu.Files.WLD.Fragments;
using EQEmu;

namespace EQEmu.Doors.Display3D
{
    public class PolygonDoorRender
    {
        private IEnumerable<EQEmu.Files.WLD.Polygon> _polys = null;
        private doors _door;
        private Mesh _mesh;

        public PolygonDoorRender(Mesh mesh, doors door)
        {
            _polys = mesh.Polygons;
            _mesh = mesh;
            _door = door;
        }

        public Mesh Mesh
        {
            get { return _mesh; }
        }

        public IEnumerable<EQEmu.Files.WLD.Polygon> Polygons
        {
            get { return _polys; }
        }

        public List<Transform3D> GetTransforms()
        {
            var transforms = new List<Transform3D>();

            RotateTransform3D heading = new RotateTransform3D();
            var rot = Door.heading / 512 * 360;
            heading.Rotation = new AxisAngleRotation3D(new Vector3D(0, 0, 1), rot + 90);

            ScaleTransform3D flip = new ScaleTransform3D();
            flip.ScaleX = -1;

            ScaleTransform3D scale = new ScaleTransform3D();
            scale.ScaleX = _door.size / 100.0;
            scale.ScaleY = _door.size / 100.0;
            scale.ScaleZ = _door.size / 100.0;

            TranslateTransform3D translate = new TranslateTransform3D();
            translate.OffsetX = Door.pos_x;
            translate.OffsetY = Door.pos_y;
            translate.OffsetZ = Door.pos_z;

            transforms.Add(heading);
            transforms.Add(flip);
            transforms.Add(scale);
            transforms.Add(translate);

            return transforms;
        }

        public doors Door
        {
            get { return _door; }
        }
    }

    public class ZoneDoorsDisplay3D : IDisposable, IViewConstrainer
    {
        private EQEmu.Files.WLD.WLD _wld = null;
        private GeometryModel3D _doorAxisModel;
        private ViewConstraints _constraints = null;
        private ZoneDoors _zoneDoors;
        private Model3D _model = new Model3DGroup();
        private Dictionary<doors, Model3DCollection> _mapping = new Dictionary<doors, Model3DCollection>();

        public ZoneDoorsDisplay3D(ZoneDoors zoneDoors)
        {
            _zoneDoors = zoneDoors;
            _zoneDoors.DoorsCollectionChanged += _zoneDoors_DoorsCollectionChanged;
            MeshBuild();

            foreach (var door in _zoneDoors.Doors)
            {
                CreateDoor(door);
            }
        }

        public EQEmu.Files.WLD.WLD WldFile
        {
            get { return _wld; }
            set
            {
                _wld = value;
                if (_wld != null)
                {
                    _wld.ResolveMeshNames();
                    if (_zoneDoors != null)
                    {
                        foreach (var door in _zoneDoors.Doors)
                        {
                            CreateDoor(door);
                        }
                    }
                }
            }
        }
        
        private GeometryModel3D GetDoorAxisModel()
        {
            return _doorAxisModel.CloneCurrentValue();
        }

        private void MeshBuild()
        {
            //build the reusable box for the nodes
            MeshBuilder builder = new MeshBuilder();
            var center = new Point3D();
            builder.AddArrow(center, new Point3D(0, 5, 0), 0.2, 1);
            builder.AddBox(center, 0.2, 0.2, 20);
            _doorAxisModel = new GeometryModel3D(builder.ToMesh(), Materials.Red);
        }

        void _zoneDoors_DoorsCollectionChanged(object sender, DoorsCollectionChangedEventArgs args)
        {
            if (args.AddedDoors != null)
            {
                foreach (var door in args.AddedDoors)
                {
                    CreateDoor(door);                    
                }
            }

            if (args.RemovedDoors != null)
            {
                foreach (var door in args.RemovedDoors)
                {
                    RemoveDoor(door);
                }
            }            
        }

        private void CreateDoor(doors door)
        {
            Model3DGroup group = Model as Model3DGroup;
            var d = door as INotifyPropertyChanged;

            Model3DCollection collection = new Model3DCollection();

            if (_mapping.ContainsKey(door) && _mapping[door] != null)
            {
                foreach (Model3D model in _mapping[door])
                {
                    group.Children.Remove(model);
                }
            }
            else
            {
                if (d != null)
                {
                    d.PropertyChanged += door_PropertyChanged;
                }
            }

            //if (_constraints != null &&
            //    !_constraints.IsPointWithinConstraints(new Point3D(door.pos_x,.x, spawn.y, spawn.z))) return;

            _mapping[door] = collection;

            #region build the door axis
            var axisModel = GetDoorAxisModel();
            Transform3DGroup transforms = new Transform3DGroup();
            Transform3D headingRotate = new RotateTransform3D()
            {
                CenterX = door.pos_x,
                CenterY = door.pos_y,
                CenterZ = door.pos_z,
                Rotation = new AxisAngleRotation3D(
                    new Vector3D(0, 0, -1), door.heading / 512 * 360 - 90)
            };

            transforms.Children.Add(new TranslateTransform3D(door.pos_x, door.pos_y, door.pos_z));
            transforms.Children.Add(headingRotate);
            axisModel.Transform = transforms;
            collection.Add(axisModel);
            #endregion

            #region render the door model
            if (_wld != null)
            {
                string rem = "_DMSPRITEDEF";
                PolygonDoorRender dr = null;
                var mesh = _wld.ZoneMeshes.FirstOrDefault(x => x.FragmentName.Replace(rem, "") == door.name);
                if (mesh != null)
                {
                    dr = new PolygonDoorRender(mesh, door);
                    var builder = new MeshBuilder();
                    var tforms = dr.GetTransforms();
                    Material mat = null;
                    if (dr.Polygons.ElementAt(0).BitmapInfo != null)
                    {
                        var img = dr.Polygons.ElementAt(0).BitmapInfo.Image;
                        var brush = new System.Windows.Media.ImageBrush(img);
                        brush.ViewportUnits = System.Windows.Media.BrushMappingMode.Absolute;
                        brush.TileMode = System.Windows.Media.TileMode.Tile;
                        mat = HelixToolkit.Wpf.MaterialHelper.CreateMaterial(brush);
                    }
                    else
                    {
                        mat = Materials.LightGray;
                    }

                    foreach (var poly in dr.Polygons)
                    {
                        Point3D p1 = new Point3D(poly.V1.X, poly.V1.Y, poly.V1.Z);
                        Point3D p2 = new Point3D(poly.V2.X, poly.V2.Y, poly.V2.Z);
                        Point3D p3 = new Point3D(poly.V3.X, poly.V3.Y, poly.V3.Z);

                        foreach (var transform in tforms)
                        {
                            p1 = transform.Transform(p1);
                            p2 = transform.Transform(p2);
                            p3 = transform.Transform(p3);
                        }

                        var t1 = new System.Windows.Point(poly.V1.U, 1 - poly.V1.V);
                        var t2 = new System.Windows.Point(poly.V2.U, 1 - poly.V2.V);
                        var t3 = new System.Windows.Point(poly.V3.U, 1 - poly.V3.V);
                        builder.AddTriangle(p1, p2, p3, t1, t2, t3);
                    }
                    collection.Add(new GeometryModel3D(builder.ToMesh(), mat));
                }
            }
            #endregion

            foreach (Model3D model in collection)
            {
                group.Children.Add(model);
            }
        }

        private void RemoveDoor(doors door)
        {
            Model3DGroup group = Model as Model3DGroup;
            var d = door as INotifyPropertyChanged;

            foreach (Model3D model in _mapping[door])
            {
                group.Children.Remove(model);
            }

            if (d != null)
            {
                d.PropertyChanged -= door_PropertyChanged;
            }

            _mapping[door] = null;
        }

        protected virtual void Dispose(bool disposeAll)
        {
            if (_zoneDoors != null)
            {
                _zoneDoors.DoorsCollectionChanged -= _zoneDoors_DoorsCollectionChanged;
            }

            foreach (var door in _zoneDoors.Doors.Cast<INotifyPropertyChanged>())
            {
                door.PropertyChanged -= door_PropertyChanged;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        void door_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            CreateDoor(sender as doors);
        }

        public void UpdateView(ViewConstraints constraints)
        {
            _constraints = constraints;
        }

        public Model3D Model
        {
            get
            {
                return _model;
            }
        }
    }
}
